基本语法

语句

var a=1+3;

分号语句结束。

变量

JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。

var a = 1;
a = ‘hello’;

变量提升
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。

标识符

中文是合法的标识符,可以用作变量名。
第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。

注释

//单行
/*多行*/
由于历史上 JavaScript 可以兼容 HTML 代码的注释,所以也被视为合法的单行注释。

区块

JavaScript 使用大括号,将多个相关的语句组合在一起,称为“区块”(block)。
对于var命令来说,JavaScript 的区块不构成单独的作用域(scope)。

条件语句

if和switch 还有三元运算符
if switch与c一样。

循环语句

while \for\ do..while
while for do..while语法与c一样。
break退出循环,continue退出本轮循环。

JavaScript 语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置。与汇编语言类似。
标签:标签通常与break语句和continue语句配合使用,跳出特定的循环.

数据类型

概述

JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值,本教程不涉及。)

JavaScript中的值分为2大类:基本类型和引用类型。
基本类型:
数字类型:Number;字符串类型:String;布尔类型:Boolean(true和false);Undefined;Null。

引用类型:
对象。

对象是最复杂的数据类型,又可以分成三个子类型。

狭义的对象和数组是两种不同的数据组合方式,除非特别声明,本教程的”对象“都特指狭义的对象。函数其实是处理数据的方法,JavaScript 把它当成一种数据类型,可以赋值给变量,这为编程带来了很大的灵活性,也为 JavaScript 的“函数式编程”奠定了基础。

在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。

对基本类型,是按值访问的,即通过值复制的方式来赋值和传递。
对引用类型,是按引用访问的,即通过引用复制的方式赋值和传递。

当一个变量进行赋值操作时,就是在重新将变量进行指向。

typeof 运算符

JavaScript 有三种方法,可以确定一个值到底是什么类型。


typeof可以用来检查一个没有声明的变量,而不报错。

instanceof运算符可以区分数组和对象。

typeof null的类型是object,这是由于历史原因造成的。

null 和 undefined

null与undefined都可以表示“没有”,含义非常相似。将一个变量赋值为undefined或null,老实说,语法效果几乎没区别。

在if语句中,它们都会被自动转为false,相等运算符(==)甚至直接报告两者相等。

区别是这样的:null是一个表示“空”的对象,转为数值时为0;undefined是一个表示”此处无定义”的原始值,转为数值时为NaN。

布尔值

如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true。


空数组([])和空对象({})对应的布尔值,都是true。

数值

概述

JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数。这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算。由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。

数值精度

根据国际标准 IEEE 754,JavaScript 浮点数的64个二进制位,从最左边开始,是这样组成的。


有效数字的第一位默认总是1。

数值范围

如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位),那么就会发生为“负向溢出”,即 JavaScript 无法表示这么小的数,这时会直接返回0。

数值的表示法

JavaScript 的数值有多种表示方法,可以用字面形式直接表示,比如35(十进制)和0xFF(十六进制)。
数值也可以采用科学计数法表示。

科学计数法允许字母e或E的后面,跟着一个整数,表示这个数值的指数部分。

以下两种情况,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
(1)小数点前的数字多于21位。
(2)小数点后的零多于5个。

数值的进制

JavaScript 对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制。


默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制。

如果八进制、十六进制、二进制的数值里面,出现不属于该进制的数字,就会报错。

有前导0的数值会被视为八进制,但是如果前导0后面有数字8和9,则该数值被视为十进制。

特殊数值

正零和负零
JavaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。几乎所有场合,正零和负零都会被当作正常的0。唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的。上面的代码之所以出现这样结果,是因为除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的。
NaN
NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。

0除以0也会得到NaN。

NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。

NaN不等于任何值,包括它本身。

数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。

NaN在布尔运算时被当作false。

NaN与任何数(包括它自己)的运算,得到的都是NaN。
Infinity
Infinity表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity。

Infinity有正负之分,Infinity表示正的无穷,-Infinity表示负的无穷。

由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript 都不报错,而是返回Infinity,所以单纯的数学运算几乎没有可能抛出错误。

Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)。

Infinity与NaN比较,总是返回false。

Infinity的四则运算,符合无穷的数学计算规则。

0乘以Infinity,返回NaN;0除以Infinity,返回0;Infinity除以0,返回Infinity。

Infinity减去或除以Infinity,得到NaN。

Infinity与null计算时,null会转成0,等同于与0的计算。

Infinity与undefined计算,返回的都是NaN。

与数值相关的全局方法

parseInt方法用于将字符串转为整数。如果字符串头部有空格,空格会被自动去除。如果parseInt的参数不是字符串,则会先转为字符串再转换。

字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。

所以,parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN。

如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析。

如果字符串以0开头,将其按照10进制解析。

对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。

进制转换
parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。

如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略。

如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN。

前面说过,如果parseInt的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。

JavaScript 不再允许将带有前缀0的数字视为八进制数,而是要求忽略这个0。但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。

parseFloat()

parseFloat方法用于将一个字符串转为浮点数。

如果字符串符合科学计数法,则会进行相应的转换。

如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。

parseFloat方法会自动过滤字符串前导的空格。

如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN。

上面代码中,尤其值得注意,parseFloat会将空字符串转为NaN。这些特点使得parseFloat的转换结果不同于Number函数。

isNaN
isNaN方法可以用来判断一个值是否为NaN。

但是,isNaN只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true,这一点要特别引起注意。也就是说,isNaN为true的值,有可能不是NaN,而是一个字符串。

出于同样的原因,对于对象和数组,isNaN也返回true。

但是,对于空数组和只有一个数值成员的数组,isNaN返回false。

上面代码之所以返回false,原因是这些数组能被Number函数转成数值。

因此,使用isNaN之前,最好判断一下数据类型。

判断NaN更可靠的方法是,利用NaN为唯一不等于自身的值的这个特点,进行判断。

isFinite()
isFinite方法返回一个布尔值,表示某个值是否为正常的数值。

除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true。

字符串

概述

字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。

单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号。

如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此。

字符串默认只能写在一行内,分成多行将会报错。

如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。注意,反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。

连接运算符(+)可以连接多个单行字符串,将长字符串拆成多行书写,输出的时候也是单行。

转义

反斜杠还有三种特殊用法。

如果在非特殊字符前面使用反斜杠,则反斜杠会被省略。如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前面需要再加一个反斜杠,用来对自身转义。

字符串与数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。

但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。

字符串内部的单个字符无法改变和增删,这些操作会默默地失败。

length 属性

length属性返回字符串的长度,该属性也是无法改变的。但是不会报错。

字符集

JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。

JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成\uxxxx的形式,其中xxxx代表该字符的 Unicode 码点。比如,\u00A9代表版权符号。

解析代码的时候,JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式表示。输出给用户的时候,所有字符都会转成字面形式。

每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存。也就是说,JavaScript 的单位字符长度固定为16位长度,即2个字节

但是,UTF-16 有两种长度。对于码点在U+0000到U+FFFF之间的字符,长度为16位(即2个字节);对于码点在U+10000到U+10FFFF之间的字符,长度为32位(即4个字节),而且前两个字节在0xD800到0xDBFF之间,后两个字节在0xDC00到0xDFFF之间。

JavaScript 对 UTF-16 的支持是不完整的,由于历史原因,只支持两字节的字符,不支持四字节的字符。

总结一下,对于码点在U+10000到U+10FFFF之间的字符,JavaScript 总是认为它们是两个字符(length属性为2)。所以处理的时候,必须把这一点考虑在内,也就是说,JavaScript 返回的字符串长度可能是不正确的。

Base64 转码

有时,文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码。

所谓 Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+和/这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。

JavaScript 原生提供两个 Base64 相关的方法。


两个方法不适合非 ASCII 码的字符,会报错。

要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。

对象

概述

简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。

键名
对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以。

如果键名是数值,会被自动转为字符串。

如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。

对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

如果属性的值还是一个对象,就形成了链式引用。

对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。

属性可以动态创建,不必在对象声明时就指定。

对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

其中任何一个变量添加属性,另一个变量都可以读写该属性。

如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。

表达式还是语句

对象采用大括号表示,这导致了一个问题:如果行首是一个大括号,它到底是表达式还是语句?

V8 引擎规定,如果行首是大括号,一律解释为对象。不过,为了避免歧义,最好还是在大括号前加上圆括号。

属性的操作

读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。

数字键可以不加引号,因为会自动转成字符串。

注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。

属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

JavaScript 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

查看所有属性

查看一个对象本身的所有属性,可以使用Object.keys方法。

delete 命令

delete命令用于删除对象的属性,删除成功后返回true。delete A[属性名];。

注意,删除一个不存在的属性,delete不报错,而且返回true。因此,不能根据delete命令的结果,认定某个属性是存在的。

只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。

注意,delete命令只能删除对象本身的属性,无法删除继承的属性。即为即使delete返回true,该属性依然可能读取到值。

in 运算符

in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false。

in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。

for…in 循环

for…in循环有两个使用注意点。

如果继承的属性是可遍历的,那么就会被for…in循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for…in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。

with 语句

它的作用是操作同一个对象的多个属性时,提供一些书写的方便。

注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。

因为with区块没有改变作用域,它的内部依然是当前作用域。这造成了with语句的一个很大的弊病,就是绑定对象不明确。

因此,建议不要使用with语句,可以考虑用一个临时变量代替with。

数组

定义

数组用方括号表示。任何类型的数据,都可以放入数组。

数组本质

本质上,数组属于一种特殊的对象。数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。
JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。

length属性

清空数组的一个有效方法,就是将length属性设为0。
如果人为设置length大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。

in运算符

检查某个键名是否存在的运算符in,适用于对象,也适用于数组。

for…in 循环和数组的遍历

for…in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。

但是,for…in不仅会遍历数组所有的数字键,还会遍历非数字键。所以,不推荐使用for…in遍历数组。
数组的遍历可以考虑使用for循环或while循环。

数组的forEach方法,也可以用来遍历数组。

数组的空位

当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。
数组的空位不影响length属性。数组最后一个成员后面有一个逗号,这不影响length属性的值。
数组的空位是可以读取的,返回undefined。
使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性

类似数组的对象

如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)。

“类似数组的对象”并不是数组,因为它们不具备数组特有的方法。对象obj没有数组的push方法,使用该方法就会报错。

“类似数组的对象”的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但是有一个问题,这种length属性不是动态值,不会随着成员的变化而变化。

典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串。

数组的slice方法可以将“类似数组的对象”变成真正的数组。

除了转为真正的数组,“类似数组的对象”还有一个办法可以使用数组的方法,就是通过call()把数组的方法放到对象上面。

字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call遍历。

注意,这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。